using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;

namespace Beaker.OpenCube.Render.Maths
{
	[DebuggerDisplay( "({X},{Y})" )]
	public struct Point2
		: IEquatable<Point2>
	{
		public static readonly Point2 Zero = new Point2( 0d, 0d );

		private readonly double _x;
		private readonly double _y;

		public Point2( double x, double y )
		{
			_x = x;
			_y = y;
		}

		#region Value

		/// <summary>
		/// Gets the X coordinate of the point
		/// </summary>
		[Pure]
		public double X
		{
			
			get { return _x; }
		}

		/// <summary>
		/// Gets the Y coordinate of the point
		/// </summary>
		[Pure]
		public double Y
		{
			
			get { return _y; }
		}

		#endregion
		#region Comparison

		public override bool Equals( object obj )
		{
			if( ReferenceEquals( null, obj ) ) return false;
			return obj is Point2 && Equals( (Point2)obj );
		}

		public bool Equals( Point2 other )
		{
			return _x.Equals( other._x ) && _y.Equals( other._y );
		}

		public override int GetHashCode()
		{
			unchecked
			{
				int hashCode = _x.GetHashCode();
				hashCode = ( hashCode * 397 ) ^ _y.GetHashCode();
				return hashCode;
			}
		}

		[Pure]
		
		public static bool operator ==( Point2 left, Point2 right )
		{
			return left.Equals( right );
		}

		[Pure]
		
		public static bool operator !=( Point2 left, Point2 right )
		{
			return !left.Equals( right );
		}

		#endregion
		#region Addition

		/// <summary>
		/// Adds the specified vector to the point.
		/// </summary>
		/// <param name="vectorToAdd">The vector to add.</param>
		/// <returns>A new <see cref="Point2"/> that is the result of the operation.</returns>
		[Pure]
		
		public Point2 Add( Vector2 vectorToAdd )
		{
			return
				new Point2(
					_x + vectorToAdd.X,
					_y + vectorToAdd.Y
				);
		}

		/// <summary>
		/// Adds the specified vector to the point.
		/// </summary>
		/// <param name="left">The point to add the vector to.</param>
		/// <param name="right">The vector to add to the point.</param>
		/// <returns>
		/// A new <see cref="Point2" /> that is the result of the operation.
		/// </returns>
		[Pure]
		
		public static Point2 Add( Point2 left, Vector2 right )
		{
			return left.Add( right );
		}

		/// <summary>
		/// Adds the specified vector to the point.
		/// </summary>
		/// <param name="left">The point to add the vector to.</param>
		/// <param name="vectorToAdd">The vector to add to the point.</param>
		/// <returns>
		/// A new <see cref="Point2" /> that is the result of the operation.
		/// </returns>
		[Pure]
		
		public static Point2 operator +( Point2 left, Vector2 vectorToAdd )
		{
			return left.Add( vectorToAdd );
		}

		#endregion
		#region Subraction

		[Pure]
		
		public Vector2 Subtract( Point2 other )
		{
			return
				new Vector2(
					_x - other._x,
					_y - other._y
				);
		}


		[Pure]
		
		public static Vector2 Subtract( Point2 left, Point2 right )
		{
			return left.Subtract( right );
		}

		[Pure]
		
		public static Vector2 operator -( Point2 left, Point2 right )
		{
			return left.Subtract( right );
		}

		/// <summary>
		/// Subtracts the specified vector from the point.
		/// </summary>
		/// <param name="vectorToSubtract">The vector to subtract.</param>
		/// <returns>
		/// A new <see cref="Point" /> that is the result of the operation.
		/// </returns>
		[Pure]
		
		public Point2 Subtract( Vector2 vectorToSubtract )
		{
			return
				new Point2(
					_x - vectorToSubtract.X,
					_y - vectorToSubtract.Y
				);
		}

		/// <summary>
		/// Subtracts the specified vector from the point.
		/// </summary>
		/// <param name="left">The point to subtract the vector from.</param>
		/// <param name="right">The vector to subract from the point.</param>
		/// <returns>
		/// A new <see cref="Point" /> that is the result of the operation.
		/// </returns>
		[Pure]
		
		public static Point2 Subtract( Point2 left, Vector2 right )
		{
			return left.Subtract( right );
		}

		/// <summary>
		/// Subtracts the specified vector from the point.
		/// </summary>
		/// <param name="left">The point to subtract the vector from.</param>
		/// <param name="vectorToSubract">The vector to subract from the point.</param>
		/// <returns>
		/// A new <see cref="Point" /> that is the result of the operation.
		/// </returns>
		[Pure]
		
		public static Point2 operator -( Point2 left, Vector2 vectorToSubract )
		{
			return left.Subtract( vectorToSubract );
		}

		#endregion
		#region Multiplication

		public Point2 Multiply( double d )
		{
			return 
				new Point2(
					_x * d,
					_y * d
				);
		}

		public static Point2 Multiply( Point2 left, double right )
		{
			return left.Multiply( right );
		}

		public static Point2 operator *( Point2 left, double right )
		{
			return left.Multiply( right );
		}

		#endregion
		#region Conversion

		[Pure]
		
		public static explicit operator Vector2( Point2 point )
		{
			return
				new Vector2(
					point.X,
					point.Y
				);
		}

		#endregion
	}
}